/*
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
**
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of version 2 of the GNU Library General
** Public License as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Library General Public License for more details.  To obtain a
** copy of the GNU Library General Public License, write to the Free
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** Any permitted reproduction of these routines, in whole or in part,
** must bear this legend.
**
**
** nes_apu.c
**
** NES APU emulation
** $Id: nes_apu.c,v 1.2 2001/04/27 14:37:11 neil Exp $
*/

#include <string.h>
#include <noftypes.h>
#include <log.h>
#include <nes_apu.h>
#include "nes6502.h"

#define APU_OVERSAMPLE
#define APU_VOLUME_DECAY(x) ((x) -= ((x) >> 7))

/* the following seem to be the correct (empirically determined)
** relative volumes between the sound channels
*/
#define APU_RECTANGLE_OUTPUT(channel) (apu.rectangle[channel].output_vol)
#define APU_TRIANGLE_OUTPUT (apu.triangle.output_vol + (apu.triangle.output_vol >> 2))
#define APU_NOISE_OUTPUT ((apu.noise.output_vol + apu.noise.output_vol + apu.noise.output_vol) >> 2)
#define APU_DMC_OUTPUT ((apu.dmc.output_vol + apu.dmc.output_vol + apu.dmc.output_vol) >> 2)

/* active APU */
static apu_t apu;

/* look up table madness */
static int32 decay_lut[16];
static int vbl_lut[32];
static int trilength_lut[128];

/* noise lookups for both modes */
#ifndef REALTIME_NOISE
static int8 noise_long_lut[APU_NOISE_32K];
static int8 noise_short_lut[APU_NOISE_93];
#endif /* !REALTIME_NOISE */

/* vblank length table used for rectangles, triangle, noise */
static const uint8 vbl_length[32] = {5, 127, 10, 1, 19, 2,  40, 3,  80, 4,  30, 5,  7, 6,  13, 7,
                                     6, 8,   12, 9, 24, 10, 48, 11, 96, 12, 36, 13, 8, 14, 16, 15};

/* frequency limit of rectangle channels */
static const int freq_limit[8] = {0x3FF, 0x555, 0x666, 0x71C, 0x787, 0x7C1, 0x7E0, 0x7F0};

/* noise frequency lookup table */
static const int noise_freq[16] = {4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068};

/* DMC transfer freqs */
const int dmc_clocks[16] = {428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 85, 72, 54};

/* ratios of pos/neg pulse for rectangle waves */
static const int duty_flip[4] = {2, 4, 8, 12};

void apu_setcontext(apu_t* src_apu) { apu = *src_apu; }

void apu_getcontext(apu_t* dest_apu) { *dest_apu = apu; }

void apu_setchan(int chan, bool enabled)
{
    if (enabled)
        apu.mix_enable |= (1 << chan);
    else
        apu.mix_enable &= ~(1 << chan);
}

/* emulation of the 15-bit shift register the
** NES uses to generate pseudo-random series
** for the white noise channel
*/
#ifdef REALTIME_NOISE
INLINE int8 shift_register15(uint8 xor_tap)
{
    static int sreg = 0x4000;
    int bit0, tap, bit14;

    bit0 = sreg & 1;
    tap = (sreg & xor_tap) ? 1 : 0;
    bit14 = (bit0 ^ tap);
    sreg >>= 1;
    sreg |= (bit14 << 14);
    return (bit0 ^ 1);
}
#else  /* !REALTIME_NOISE */
static void shift_register15(int8* buf, int count)
{
    static int sreg = 0x4000;
    int bit0, bit1, bit6, bit14;

    if (count == APU_NOISE_93)
    {
        while (count--)
        {
            bit0 = sreg & 1;
            bit6 = (sreg & 0x40) >> 6;
            bit14 = (bit0 ^ bit6);
            sreg >>= 1;
            sreg |= (bit14 << 14);
            *buf++ = bit0 ^ 1;
        }
    }
    else /* 32K noise */
    {
        while (count--)
        {
            bit0 = sreg & 1;
            bit1 = (sreg & 2) >> 1;
            bit14 = (bit0 ^ bit1);
            sreg >>= 1;
            sreg |= (bit14 << 14);
            *buf++ = bit0 ^ 1;
        }
    }
}
#endif /* !REALTIME_NOISE */

/* RECTANGLE WAVE
** ==============
** reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle
** reg1: 0-2=sweep shifts, 3=sweep inc/dec, 4-6=sweep length, 7=sweep on
** reg2: 8 bits of freq
** reg3: 0-2=high freq, 7-4=vbl length counter
*/
#ifdef APU_OVERSAMPLE

#define APU_MAKE_RECTANGLE(ch)                                                                                                 \
    static int32 apu_rectangle_##ch(void)                                                                                      \
    {                                                                                                                          \
        int32 output, total;                                                                                                   \
        int num_times;                                                                                                         \
                                                                                                                               \
        APU_VOLUME_DECAY(apu.rectangle[ch].output_vol);                                                                        \
                                                                                                                               \
        if (false == apu.rectangle[ch].enabled || 0 == apu.rectangle[ch].vbl_length)                                           \
            return APU_RECTANGLE_OUTPUT(ch);                                                                                   \
                                                                                                                               \
        /* vbl length counter */                                                                                               \
        if (false == apu.rectangle[ch].holdnote)                                                                               \
            apu.rectangle[ch].vbl_length--;                                                                                    \
                                                                                                                               \
        /* envelope decay at a rate of (env_delay + 1) / 240 secs */                                                           \
        apu.rectangle[ch].env_phase -= 4; /* 240/60 */                                                                         \
        while (apu.rectangle[ch].env_phase < 0)                                                                                \
        {                                                                                                                      \
            apu.rectangle[ch].env_phase += apu.rectangle[ch].env_delay;                                                        \
                                                                                                                               \
            if (apu.rectangle[ch].holdnote)                                                                                    \
                apu.rectangle[ch].env_vol = (apu.rectangle[ch].env_vol + 1) & 0x0F;                                            \
            else if (apu.rectangle[ch].env_vol < 0x0F)                                                                         \
                apu.rectangle[ch].env_vol++;                                                                                   \
        }                                                                                                                      \
                                                                                                                               \
        /* TODO: find true relation of freq_limit to register values */                                                        \
        if (apu.rectangle[ch].freq < 8 ||                                                                                      \
            (false == apu.rectangle[ch].sweep_inc && apu.rectangle[ch].freq > apu.rectangle[ch].freq_limit))                   \
            return APU_RECTANGLE_OUTPUT(ch);                                                                                   \
                                                                                                                               \
        /* frequency sweeping at a rate of (sweep_delay + 1) / 120 secs */                                                     \
        if (apu.rectangle[ch].sweep_on && apu.rectangle[ch].sweep_shifts)                                                      \
        {                                                                                                                      \
            apu.rectangle[ch].sweep_phase -= 2; /* 120/60 */                                                                   \
            while (apu.rectangle[ch].sweep_phase < 0)                                                                          \
            {                                                                                                                  \
                apu.rectangle[ch].sweep_phase += apu.rectangle[ch].sweep_delay;                                                \
                                                                                                                               \
                if (apu.rectangle[ch].sweep_inc) /* ramp up */                                                                 \
                {                                                                                                              \
                    if (0 == ch)                                                                                               \
                        apu.rectangle[ch].freq += ~(apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts);                 \
                    else                                                                                                       \
                        apu.rectangle[ch].freq -= (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts);                  \
                }                                                                                                              \
                else /* ramp down */                                                                                           \
                {                                                                                                              \
                    apu.rectangle[ch].freq += (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts);                      \
                }                                                                                                              \
            }                                                                                                                  \
        }                                                                                                                      \
                                                                                                                               \
        apu.rectangle[ch].accum -= apu.cycle_rate;                                                                             \
        if (apu.rectangle[ch].accum >= 0)                                                                                      \
            return APU_RECTANGLE_OUTPUT(ch);                                                                                   \
                                                                                                                               \
        if (apu.rectangle[ch].fixed_envelope)                                                                                  \
            output = apu.rectangle[ch].volume << 8; /* fixed volume */                                                         \
        else                                                                                                                   \
            output = (apu.rectangle[ch].env_vol ^ 0x0F) << 8;                                                                  \
                                                                                                                               \
        num_times = total = 0;                                                                                                 \
                                                                                                                               \
        while (apu.rectangle[ch].accum < 0)                                                                                    \
        {                                                                                                                      \
            apu.rectangle[ch].accum += apu.rectangle[ch].freq + 1;                                                             \
            apu.rectangle[ch].adder = (apu.rectangle[ch].adder + 1) & 0x0F;                                                    \
                                                                                                                               \
            if (apu.rectangle[ch].adder < apu.rectangle[ch].duty_flip)                                                         \
                total += output;                                                                                               \
            else                                                                                                               \
                total -= output;                                                                                               \
                                                                                                                               \
            num_times++;                                                                                                       \
        }                                                                                                                      \
                                                                                                                               \
        apu.rectangle[ch].output_vol = total / num_times;                                                                      \
        return APU_RECTANGLE_OUTPUT(ch);                                                                                       \
    }

#else /* !APU_OVERSAMPLE */
#define APU_MAKE_RECTANGLE(ch)                                                                                                 \
    static int32 apu_rectangle_##ch(void)                                                                                      \
    {                                                                                                                          \
        int32 output;                                                                                                          \
                                                                                                                               \
        APU_VOLUME_DECAY(apu.rectangle[ch].output_vol);                                                                        \
                                                                                                                               \
        if (false == apu.rectangle[ch].enabled || 0 == apu.rectangle[ch].vbl_length)                                           \
            return APU_RECTANGLE_OUTPUT(ch);                                                                                   \
                                                                                                                               \
        /* vbl length counter */                                                                                               \
        if (false == apu.rectangle[ch].holdnote)                                                                               \
            apu.rectangle[ch].vbl_length--;                                                                                    \
                                                                                                                               \
        /* envelope decay at a rate of (env_delay + 1) / 240 secs */                                                           \
        apu.rectangle[ch].env_phase -= 4; /* 240/60 */                                                                         \
        while (apu.rectangle[ch].env_phase < 0)                                                                                \
        {                                                                                                                      \
            apu.rectangle[ch].env_phase += apu.rectangle[ch].env_delay;                                                        \
                                                                                                                               \
            if (apu.rectangle[ch].holdnote)                                                                                    \
                apu.rectangle[ch].env_vol = (apu.rectangle[ch].env_vol + 1) & 0x0F;                                            \
            else if (apu.rectangle[ch].env_vol < 0x0F)                                                                         \
                apu.rectangle[ch].env_vol++;                                                                                   \
        }                                                                                                                      \
                                                                                                                               \
        /* TODO: find true relation of freq_limit to register values */                                                        \
        if (apu.rectangle[ch].freq < 8 ||                                                                                      \
            (false == apu.rectangle[ch].sweep_inc && apu.rectangle[ch].freq > apu.rectangle[ch].freq_limit))                   \
            return APU_RECTANGLE_OUTPUT(ch);                                                                                   \
                                                                                                                               \
        /* frequency sweeping at a rate of (sweep_delay + 1) / 120 secs */                                                     \
        if (apu.rectangle[ch].sweep_on && apu.rectangle[ch].sweep_shifts)                                                      \
        {                                                                                                                      \
            apu.rectangle[ch].sweep_phase -= 2; /* 120/60 */                                                                   \
            while (apu.rectangle[ch].sweep_phase < 0)                                                                          \
            {                                                                                                                  \
                apu.rectangle[ch].sweep_phase += apu.rectangle[ch].sweep_delay;                                                \
                                                                                                                               \
                if (apu.rectangle[ch].sweep_inc) /* ramp up */                                                                 \
                {                                                                                                              \
                    if (0 == ch)                                                                                               \
                        apu.rectangle[ch].freq += ~(apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts);                 \
                    else                                                                                                       \
                        apu.rectangle[ch].freq -= (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts);                  \
                }                                                                                                              \
                else /* ramp down */                                                                                           \
                {                                                                                                              \
                    apu.rectangle[ch].freq += (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts);                      \
                }                                                                                                              \
            }                                                                                                                  \
        }                                                                                                                      \
                                                                                                                               \
        apu.rectangle[ch].accum -= apu.cycle_rate;                                                                             \
        if (apu.rectangle[ch].accum >= 0)                                                                                      \
            return APU_RECTANGLE_OUTPUT(ch);                                                                                   \
                                                                                                                               \
        while (apu.rectangle[ch].accum < 0)                                                                                    \
        {                                                                                                                      \
            apu.rectangle[ch].accum += (apu.rectangle[ch].freq + 1);                                                           \
            apu.rectangle[ch].adder = (apu.rectangle[ch].adder + 1) & 0x0F;                                                    \
        }                                                                                                                      \
                                                                                                                               \
        if (apu.rectangle[ch].fixed_envelope)                                                                                  \
            output = apu.rectangle[ch].volume << 8; /* fixed volume */                                                         \
        else                                                                                                                   \
            output = (apu.rectangle[ch].env_vol ^ 0x0F) << 8;                                                                  \
                                                                                                                               \
        if (0 == apu.rectangle[ch].adder)                                                                                      \
            apu.rectangle[ch].output_vol = output;                                                                             \
        else if (apu.rectangle[ch].adder == apu.rectangle[ch].duty_flip)                                                       \
            apu.rectangle[ch].output_vol = -output;                                                                            \
                                                                                                                               \
        return APU_RECTANGLE_OUTPUT(ch);                                                                                       \
    }

#endif /* !APU_OVERSAMPLE */

/* generate the functions */
APU_MAKE_RECTANGLE(0)
APU_MAKE_RECTANGLE(1)

/* TRIANGLE WAVE
** =============
** reg0: 7=holdnote, 6-0=linear length counter
** reg2: low 8 bits of frequency
** reg3: 7-3=length counter, 2-0=high 3 bits of frequency
*/
static int32 apu_triangle(void)
{
    APU_VOLUME_DECAY(apu.triangle.output_vol);

    if (false == apu.triangle.enabled || 0 == apu.triangle.vbl_length)
        return APU_TRIANGLE_OUTPUT;

    if (apu.triangle.counter_started)
    {
        if (apu.triangle.linear_length > 0)
            apu.triangle.linear_length--;
        if (apu.triangle.vbl_length && false == apu.triangle.holdnote)
            apu.triangle.vbl_length--;
    }
    else if (false == apu.triangle.holdnote && apu.triangle.write_latency)
    {
        if (--apu.triangle.write_latency == 0)
            apu.triangle.counter_started = true;
    }

    if (0 == apu.triangle.linear_length || apu.triangle.freq < 4) /* inaudible */
        return APU_TRIANGLE_OUTPUT;

    apu.triangle.accum -= apu.cycle_rate;
    while (apu.triangle.accum < 0)
    {
        apu.triangle.accum += apu.triangle.freq;
        apu.triangle.adder = (apu.triangle.adder + 1) & 0x1F;

        if (apu.triangle.adder & 0x10)
            apu.triangle.output_vol -= (2 << 8);
        else
            apu.triangle.output_vol += (2 << 8);
    }

    return APU_TRIANGLE_OUTPUT;
}

/* WHITE NOISE CHANNEL
** ===================
** reg0: 0-3=volume, 4=envelope, 5=hold
** reg2: 7=small(93 byte) sample,3-0=freq lookup
** reg3: 7-4=vbl length counter
*/
/* TODO: AAAAAAAAAAAAAAAAAAAAAAAA!  #ifdef MADNESS! */
static int32 apu_noise(void)
{
    int32 outvol;

#if defined(APU_OVERSAMPLE) && defined(REALTIME_NOISE)
#else  /* !(APU_OVERSAMPLE && REALTIME_NOISE) */
    int32 noise_bit;
#endif /* !(APU_OVERSAMPLE && REALTIME_NOISE) */
#ifdef APU_OVERSAMPLE
    int num_times;
    int32 total;
#endif /* APU_OVERSAMPLE */

    APU_VOLUME_DECAY(apu.noise.output_vol);

    if (false == apu.noise.enabled || 0 == apu.noise.vbl_length)
        return APU_NOISE_OUTPUT;

    /* vbl length counter */
    if (false == apu.noise.holdnote)
        apu.noise.vbl_length--;

    /* envelope decay at a rate of (env_delay + 1) / 240 secs */
    apu.noise.env_phase -= 4; /* 240/60 */
    while (apu.noise.env_phase < 0)
    {
        apu.noise.env_phase += apu.noise.env_delay;

        if (apu.noise.holdnote)
            apu.noise.env_vol = (apu.noise.env_vol + 1) & 0x0F;
        else if (apu.noise.env_vol < 0x0F)
            apu.noise.env_vol++;
    }

    apu.noise.accum -= apu.cycle_rate;
    if (apu.noise.accum >= 0)
        return APU_NOISE_OUTPUT;

#ifdef APU_OVERSAMPLE
    if (apu.noise.fixed_envelope)
        outvol = apu.noise.volume << 8; /* fixed volume */
    else
        outvol = (apu.noise.env_vol ^ 0x0F) << 8;

    num_times = total = 0;
#endif /* APU_OVERSAMPLE */

    while (apu.noise.accum < 0)
    {
        apu.noise.accum += apu.noise.freq;

#ifdef REALTIME_NOISE

#ifdef APU_OVERSAMPLE
        if (shift_register15(apu.noise.xor_tap))
            total += outvol;
        else
            total -= outvol;

        num_times++;
#else  /* !APU_OVERSAMPLE */
        noise_bit = shift_register15(apu.noise.xor_tap);
#endif /* !APU_OVERSAMPLE */

#else /* !REALTIME_NOISE */
        apu.noise.cur_pos++;

        if (apu.noise.short_sample)
        {
            if (APU_NOISE_93 == apu.noise.cur_pos)
                apu.noise.cur_pos = 0;
        }
        else
        {
            if (APU_NOISE_32K == apu.noise.cur_pos)
                apu.noise.cur_pos = 0;
        }

#ifdef APU_OVERSAMPLE
        if (apu.noise.short_sample)
            noise_bit = noise_short_lut[apu.noise.cur_pos];
        else
            noise_bit = noise_long_lut[apu.noise.cur_pos];

        if (noise_bit)
            total += outvol;
        else
            total -= outvol;

        num_times++;
#endif /* APU_OVERSAMPLE */
#endif /* !REALTIME_NOISE */
    }

#ifdef APU_OVERSAMPLE
    apu.noise.output_vol = total / num_times;
#else /* !APU_OVERSAMPLE */
    if (apu.noise.fixed_envelope)
        outvol = apu.noise.volume << 8; /* fixed volume */
    else
        outvol = (apu.noise.env_vol ^ 0x0F) << 8;

#ifndef REALTIME_NOISE
    if (apu.noise.short_sample)
        noise_bit = noise_short_lut[apu.noise.cur_pos];
    else
        noise_bit = noise_long_lut[apu.noise.cur_pos];
#endif /* !REALTIME_NOISE */

    if (noise_bit)
        apu.noise.output_vol = outvol;
    else
        apu.noise.output_vol = -outvol;
#endif /* !APU_OVERSAMPLE */

    return APU_NOISE_OUTPUT;
}

INLINE void apu_dmcreload(void)
{
    apu.dmc.address = apu.dmc.cached_addr;
    apu.dmc.dma_length = apu.dmc.cached_dmalength;
    apu.dmc.irq_occurred = false;
}

/* DELTA MODULATION CHANNEL
** =========================
** reg0: 7=irq gen, 6=looping, 3-0=pointer to clock table
** reg1: output dc level, 6 bits unsigned
** reg2: 8 bits of 64-byte aligned address offset : $C000 + (value * 64)
** reg3: length, (value * 16) + 1
*/
static int32 apu_dmc(void)
{
    int delta_bit;

    APU_VOLUME_DECAY(apu.dmc.output_vol);

    /* only process when channel is alive */
    if (apu.dmc.dma_length)
    {
        apu.dmc.accum -= apu.cycle_rate;

        while (apu.dmc.accum < 0)
        {
            apu.dmc.accum += apu.dmc.freq;

            delta_bit = (apu.dmc.dma_length & 7) ^ 7;

            if (7 == delta_bit)
            {
                apu.dmc.cur_byte = nes6502_getbyte(apu.dmc.address);

                /* steal a cycle from CPU*/
                nes6502_burn(1);

                /* prevent wraparound */
                if (0xFFFF == apu.dmc.address)
                    apu.dmc.address = 0x8000;
                else
                    apu.dmc.address++;
            }

            if (--apu.dmc.dma_length == 0)
            {
                /* if loop bit set, we're cool to retrigger sample */
                if (apu.dmc.looping)
                {
                    apu_dmcreload();
                }
                else
                {
                    /* check to see if we should generate an irq */
                    if (apu.dmc.irq_gen)
                    {
                        apu.dmc.irq_occurred = true;
                        if (apu.irq_callback)
                            apu.irq_callback();
                    }

                    /* bodge for timestamp queue */
                    apu.dmc.enabled = false;
                    break;
                }
            }

            /* positive delta */
            if (apu.dmc.cur_byte & (1 << delta_bit))
            {
                if (apu.dmc.regs[1] < 0x7D)
                {
                    apu.dmc.regs[1] += 2;
                    apu.dmc.output_vol += (2 << 8);
                }
            }
            /* negative delta */
            else
            {
                if (apu.dmc.regs[1] > 1)
                {
                    apu.dmc.regs[1] -= 2;
                    apu.dmc.output_vol -= (2 << 8);
                }
            }
        }
    }

    return APU_DMC_OUTPUT;
}

void apu_write(uint32 address, uint8 value)
{
    int chan;

    switch (address)
    {
    /* rectangles */
    case APU_WRA0:
    case APU_WRB0:
        chan = (address & 4) >> 2;
        apu.rectangle[chan].regs[0] = value;
        apu.rectangle[chan].volume = value & 0x0F;
        apu.rectangle[chan].env_delay = decay_lut[value & 0x0F];
        apu.rectangle[chan].holdnote = (value & 0x20) ? true : false;
        apu.rectangle[chan].fixed_envelope = (value & 0x10) ? true : false;
        apu.rectangle[chan].duty_flip = duty_flip[value >> 6];
        break;

    case APU_WRA1:
    case APU_WRB1:
        chan = (address & 4) >> 2;
        apu.rectangle[chan].regs[1] = value;
        apu.rectangle[chan].sweep_on = (value & 0x80) ? true : false;
        apu.rectangle[chan].sweep_shifts = value & 7;
        apu.rectangle[chan].sweep_delay = decay_lut[(value >> 4) & 7];
        apu.rectangle[chan].sweep_inc = (value & 0x08) ? true : false;
        apu.rectangle[chan].freq_limit = freq_limit[value & 7];
        break;

    case APU_WRA2:
    case APU_WRB2:
        chan = (address & 4) >> 2;
        apu.rectangle[chan].regs[2] = value;
        apu.rectangle[chan].freq = (apu.rectangle[chan].freq & ~0xFF) | value;
        break;

    case APU_WRA3:
    case APU_WRB3:
        chan = (address & 4) >> 2;
        apu.rectangle[chan].regs[3] = value;
        apu.rectangle[chan].vbl_length = vbl_lut[value >> 3];
        apu.rectangle[chan].env_vol = 0;
        apu.rectangle[chan].freq = ((value & 7) << 8) | (apu.rectangle[chan].freq & 0xFF);
        apu.rectangle[chan].adder = 0;
        break;

    /* triangle */
    case APU_WRC0:
        apu.triangle.regs[0] = value;
        apu.triangle.holdnote = (value & 0x80) ? true : false;

        if (false == apu.triangle.counter_started && apu.triangle.vbl_length)
            apu.triangle.linear_length = trilength_lut[value & 0x7F];

        break;

    case APU_WRC2:
        apu.triangle.regs[1] = value;
        apu.triangle.freq = (((apu.triangle.regs[2] & 7) << 8) + value) + 1;
        break;

    case APU_WRC3:

        apu.triangle.regs[2] = value;

        /* this is somewhat of a hack.  there appears to be some latency on
        ** the Real Thing between when trireg0 is written to and when the
        ** linear length counter actually begins its countdown.  we want to
        ** prevent the case where the program writes to the freq regs first,
        ** then to reg 0, and the counter accidentally starts running because
        ** of the sound queue's timestamp processing.
        **
        ** set latency to a couple hundred cycles -- should be plenty of time
        ** for the 6502 code to do a couple of table dereferences and load up
        ** the other triregs
        */
        apu.triangle.write_latency = (int)(228 / apu.cycle_rate);
        apu.triangle.freq = (((value & 7) << 8) + apu.triangle.regs[1]) + 1;
        apu.triangle.vbl_length = vbl_lut[value >> 3];
        apu.triangle.counter_started = false;
        apu.triangle.linear_length = trilength_lut[apu.triangle.regs[0] & 0x7F];
        break;

    /* noise */
    case APU_WRD0:
        apu.noise.regs[0] = value;
        apu.noise.env_delay = decay_lut[value & 0x0F];
        apu.noise.holdnote = (value & 0x20) ? true : false;
        apu.noise.fixed_envelope = (value & 0x10) ? true : false;
        apu.noise.volume = value & 0x0F;
        break;

    case APU_WRD2:
        apu.noise.regs[1] = value;
        apu.noise.freq = noise_freq[value & 0x0F];

#ifdef REALTIME_NOISE
        apu.noise.xor_tap = (value & 0x80) ? 0x40 : 0x02;
#else  /* !REALTIME_NOISE */
        /* detect transition from long->short sample */
        if ((value & 0x80) && false == apu.noise.short_sample)
        {
            /* recalculate short noise buffer */
            shift_register15(noise_short_lut, APU_NOISE_93);
            apu.noise.cur_pos = 0;
        }
        apu.noise.short_sample = (value & 0x80) ? true : false;
#endif /* !REALTIME_NOISE */
        break;

    case APU_WRD3:
        apu.noise.regs[2] = value;
        apu.noise.vbl_length = vbl_lut[value >> 3];
        apu.noise.env_vol = 0; /* reset envelope */
        break;

    /* DMC */
    case APU_WRE0:
        apu.dmc.regs[0] = value;
        apu.dmc.freq = dmc_clocks[value & 0x0F];
        apu.dmc.looping = (value & 0x40) ? true : false;

        if (value & 0x80)
        {
            apu.dmc.irq_gen = true;
        }
        else
        {
            apu.dmc.irq_gen = false;
            apu.dmc.irq_occurred = false;
        }
        break;

    case APU_WRE1: /* 7-bit DAC */
        /* add the _delta_ between written value and
        ** current output level of the volume reg
        */
        value &= 0x7F; /* bit 7 ignored */
        apu.dmc.output_vol += ((value - apu.dmc.regs[1]) << 8);
        apu.dmc.regs[1] = value;
        break;

    case APU_WRE2:
        apu.dmc.regs[2] = value;
        apu.dmc.cached_addr = 0xC000 + (uint16)(value << 6);
        break;

    case APU_WRE3:
        apu.dmc.regs[3] = value;
        apu.dmc.cached_dmalength = ((value << 4) + 1) << 3;
        break;

    case APU_SMASK:
        /* bodge for timestamp queue */
        apu.dmc.enabled = (value & 0x10) ? true : false;
        apu.enable_reg = value;

        for (chan = 0; chan < 2; chan++)
        {
            if (value & (1 << chan))
            {
                apu.rectangle[chan].enabled = true;
            }
            else
            {
                apu.rectangle[chan].enabled = false;
                apu.rectangle[chan].vbl_length = 0;
            }
        }

        if (value & 0x04)
        {
            apu.triangle.enabled = true;
        }
        else
        {
            apu.triangle.enabled = false;
            apu.triangle.vbl_length = 0;
            apu.triangle.linear_length = 0;
            apu.triangle.counter_started = false;
            apu.triangle.write_latency = 0;
        }

        if (value & 0x08)
        {
            apu.noise.enabled = true;
        }
        else
        {
            apu.noise.enabled = false;
            apu.noise.vbl_length = 0;
        }

        if (value & 0x10)
        {
            if (0 == apu.dmc.dma_length)
                apu_dmcreload();
        }
        else
        {
            apu.dmc.dma_length = 0;
        }

        apu.dmc.irq_occurred = false;
        break;

        /* unused, but they get hit in some mem-clear loops */
    case 0x4009:
    case 0x400D:
        break;

    default:
        break;
    }
}

/* Read from $4000-$4017 */
uint8 apu_read(uint32 address)
{
    uint8 value;

    switch (address)
    {
    case APU_SMASK:
        value = 0;
        /* Return 1 in 0-5 bit pos if a channel is playing */
        if (apu.rectangle[0].enabled && apu.rectangle[0].vbl_length)
            value |= 0x01;
        if (apu.rectangle[1].enabled && apu.rectangle[1].vbl_length)
            value |= 0x02;
        if (apu.triangle.enabled && apu.triangle.vbl_length)
            value |= 0x04;
        if (apu.noise.enabled && apu.noise.vbl_length)
            value |= 0x08;

        /* bodge for timestamp queue */
        if (apu.dmc.enabled)
            value |= 0x10;

        if (apu.dmc.irq_occurred)
            value |= 0x80;

        if (apu.irqclear_callback)
            value |= apu.irqclear_callback();

        break;

    default:
        value = (address >> 8); /* heavy capacitance on data bus */
        break;
    }

    return value;
}

#define CLIP_OUTPUT16(out)                                                                                                     \
    {                                                                                                                          \
        /*out <<= 1;*/                                                                                                         \
        if (out > 0x7FFF)                                                                                                      \
            out = 0x7FFF;                                                                                                      \
        else if (out < -0x8000)                                                                                                \
            out = -0x8000;                                                                                                     \
    }

void apu_process(void* buffer, int num_samples)
{
    static int32 prev_sample = 0;

    int16* buf16;
    uint8* buf8;

    if (NULL != buffer)
    {
        /* bleh */
        apu.buffer = buffer;

        buf16 = (int16*)buffer;
        buf8 = (uint8*)buffer;

        while (num_samples--)
        {
            int32 next_sample, accum = 0;

            if (apu.mix_enable & 0x01)
                accum += apu_rectangle_0();
            if (apu.mix_enable & 0x02)
                accum += apu_rectangle_1();
            if (apu.mix_enable & 0x04)
                accum += apu_triangle();
            if (apu.mix_enable & 0x08)
                accum += apu_noise();
            if (apu.mix_enable & 0x10)
                accum += apu_dmc();
            if (apu.ext && (apu.mix_enable & 0x20))
                accum += apu.ext->process();

            /* do any filtering */
            if (APU_FILTER_NONE != apu.filter_type)
            {
                next_sample = accum;

                if (APU_FILTER_LOWPASS == apu.filter_type)
                {
                    accum += prev_sample;
                    accum >>= 1;
                }
                else
                    accum = (accum + accum + accum + prev_sample) >> 2;

                prev_sample = next_sample;
            }

            /* do clipping */
            CLIP_OUTPUT16(accum);

            /* signed 16-bit output, unsigned 8-bit */
            if (16 == apu.sample_bits)
                *buf16++ = (int16)accum;
            else
                *buf8++ = (accum >> 8) ^ 0x80;
        }
    }
}

/* set the filter type */
void apu_setfilter(int filter_type) { apu.filter_type = filter_type; }

void apu_reset(void)
{
    uint32 address;

    /* initialize all channel members */
    for (address = 0x4000; address <= 0x4013; address++)
        apu_write(address, 0);

    apu_write(0x4015, 0);

    if (apu.ext && NULL != apu.ext->reset)
        apu.ext->reset();
}

void apu_build_luts(int num_samples)
{
    int i;

    /* lut used for enveloping and frequency sweeps */
    for (i = 0; i < 16; i++)
        decay_lut[i] = num_samples * (i + 1);

    /* used for note length, based on vblanks and size of audio buffer */
    for (i = 0; i < 32; i++)
        vbl_lut[i] = vbl_length[i] * num_samples;

    /* triangle wave channel's linear length table */
    for (i = 0; i < 128; i++)
        trilength_lut[i] = (int)(0.25 * i * num_samples);

#ifndef REALTIME_NOISE
    /* generate noise samples */
    shift_register15(noise_long_lut, APU_NOISE_32K);
    shift_register15(noise_short_lut, APU_NOISE_93);
#endif /* !REALTIME_NOISE */
}

void apu_setparams(double base_freq, int sample_rate, int refresh_rate, int sample_bits)
{
    apu.sample_rate = sample_rate;
    apu.refresh_rate = refresh_rate;
    apu.sample_bits = sample_bits;
    apu.num_samples = sample_rate / refresh_rate;
    if (0 == base_freq)
        apu.base_freq = APU_BASEFREQ;
    else
        apu.base_freq = base_freq;
    apu.cycle_rate = (float)(apu.base_freq / sample_rate);

    /* build various lookup tables for apu */
    apu_build_luts(apu.num_samples);

    apu_reset();
}

/* Initializes emulated sound hardware, creates waveforms/voices */
apu_t* apu_create(double base_freq, int sample_rate, int refresh_rate, int sample_bits)
{
    apu_t* temp_apu;
    int channel;

    temp_apu = malloc(sizeof(apu_t));
    if (NULL == temp_apu)
        return NULL;

    memset(temp_apu, 0, sizeof(apu_t));

    /* set the update routine */
    temp_apu->process = apu_process;
    temp_apu->ext = NULL;

    /* clear the callbacks */
    temp_apu->irq_callback = NULL;
    temp_apu->irqclear_callback = NULL;

    apu_setcontext(temp_apu);

    apu_setparams(base_freq, sample_rate, refresh_rate, sample_bits);

    for (channel = 0; channel < 6; channel++)
        apu_setchan(channel, true);

    apu_setfilter(APU_FILTER_WEIGHTED);

    apu_getcontext(temp_apu);

    return temp_apu;
}

void apu_destroy(apu_t** src_apu)
{
    if (*src_apu)
    {
        if ((*src_apu)->ext && NULL != (*src_apu)->ext->shutdown)
            (*src_apu)->ext->shutdown();
        free(*src_apu);
        *src_apu = NULL;
    }
}

void apu_setext(apu_t* src_apu, apuext_t* ext)
{
    ASSERT(src_apu);

    src_apu->ext = ext;

    /* initialize it */
    if (src_apu->ext && NULL != src_apu->ext->init)
        src_apu->ext->init();
}

/*
** $Log: nes_apu.c,v $
** Revision 1.2  2001/04/27 14:37:11  neil
** wheeee
**
** Revision 1.1  2001/04/27 12:54:40  neil
** blah
**
** Revision 1.1.1.1  2001/04/27 07:03:54  neil
** initial
**
** Revision 1.6  2000/12/08 02:36:14  matt
** bye bye apu queue (for now)
**
** Revision 1.5  2000/11/27 19:33:53  matt
** no special treatment for nsf
**
** Revision 1.4  2000/11/25 20:29:17  matt
** weighted filter is now default
**
** Revision 1.3  2000/11/21 13:28:19  matt
** take care to zero allocated mem
**
** Revision 1.2  2000/10/28 15:20:59  matt
** irq callbacks in nes_apu
**
** Revision 1.1  2000/10/24 12:19:59  matt
** changed directory structure
**
** Revision 1.44  2000/10/23 17:53:06  matt
** set ptr to NULL after freeing
**
** Revision 1.43  2000/10/17 11:56:42  matt
** selectable apu base frequency
**
** Revision 1.42  2000/10/13 12:16:01  matt
** macro-ized the stuff that should be removed
**
** Revision 1.41  2000/10/10 13:58:18  matt
** stroustrup squeezing his way in the door
**
** Revision 1.40  2000/10/03 11:56:20  matt
** better support for optional sound ext routines
**
** Revision 1.39  2000/09/27 12:26:03  matt
** changed sound accumulators back to floats
**
** Revision 1.38  2000/09/18 02:12:55  matt
** more optimizations
**
** Revision 1.37  2000/09/15 13:38:40  matt
** changes for optimized apu core
**
** Revision 1.36  2000/09/15 04:58:07  matt
** simplifying and optimizing APU core
**
** Revision 1.35  2000/09/07 21:57:14  matt
** api change
**
** Revision 1.34  2000/08/16 05:01:01  matt
** small buglet fixed
**
** Revision 1.33  2000/08/15 12:38:04  matt
** removed debug output
**
** Revision 1.32  2000/08/15 12:36:51  matt
** calling apu_process with buffer=NULL causes silent emulation of APU
**
** Revision 1.31  2000/08/11 02:27:21  matt
** general cleanups, plus apu_setparams routine
**
** Revision 1.30  2000/07/31 04:32:52  matt
** fragsize problem fixed, perhaps
**
** Revision 1.29  2000/07/30 04:32:59  matt
** no more apu_getcyclerate hack
**
** Revision 1.28  2000/07/28 03:15:46  matt
** accuracy changes for rectangle frequency sweeps
**
** Revision 1.27  2000/07/27 02:49:50  matt
** eccentricity in sweeping hardware now emulated correctly
**
** Revision 1.26  2000/07/25 02:25:14  matt
** safer apu_destroy
**
** Revision 1.25  2000/07/23 15:10:54  matt
** hacks for win32
**
** Revision 1.24  2000/07/17 01:52:31  matt
** made sure last line of all source files is a newline
**
** Revision 1.23  2000/07/10 19:24:55  matt
** irqs are not supported in NSF playing mode
**
** Revision 1.22  2000/07/10 13:54:32  matt
** using generic nes_irq() now
**
** Revision 1.21  2000/07/10 05:29:34  matt
** moved joypad/oam dma from apu to ppu
**
** Revision 1.20  2000/07/09 03:49:31  matt
** apu irqs now draw an irq line (bleh)
**
** Revision 1.19  2000/07/04 04:53:26  matt
** minor changes, sound amplification
**
** Revision 1.18  2000/07/03 02:18:53  matt
** much better external module exporting
**
** Revision 1.17  2000/06/26 11:01:55  matt
** made triangle a tad quieter
**
** Revision 1.16  2000/06/26 05:10:33  matt
** fixed cycle rate generation accuracy
**
** Revision 1.15  2000/06/26 05:00:37  matt
** cleanups
**
** Revision 1.14  2000/06/23 11:06:24  matt
** more faithful mixing of channels
**
** Revision 1.13  2000/06/23 03:29:27  matt
** cleaned up external sound inteface
**
** Revision 1.12  2000/06/20 00:08:39  matt
** bugfix to rectangle wave
**
** Revision 1.11  2000/06/13 13:48:58  matt
** fixed triangle write latency for fixed point apu cycle rate
**
** Revision 1.10  2000/06/12 01:14:36  matt
** minor change to clipping extents
**
** Revision 1.9  2000/06/09 20:00:56  matt
** fixed noise hiccup in NSF player mode
**
** Revision 1.8  2000/06/09 16:49:02  matt
** removed all floating point from sound generation
**
** Revision 1.7  2000/06/09 15:12:28  matt
** initial revision
**
*/
